/*-------------------------------------------------------------------------
 *
 * nodeRecursiveunion.c
 *	  处理 RecursiveUnion 节点的例程。
 *
 * 要实现 UNION（不含 ALL），我们需要一个哈希表来存储
 * 已经见过的元组。哈希键是由分组列计算得出的。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeRecursiveunion.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "executor/execdebug.h"
#include "executor/nodeRecursiveunion.h"
#include "miscadmin.h"
#include "utils/memutils.h"



/*
 * 初始化哈希表为空。
 */
static void fc_build_hash_table(RecursiveUnionState *fc_rustate)
{
	RecursiveUnion *fc_node = (RecursiveUnion *) fc_rustate->ps.plan;
	TupleDesc	fc_desc = ExecGetResultType(outerPlanState(fc_rustate));

	Assert(fc_node->numCols > 0);
	Assert(fc_node->numGroups > 0);

	fc_rustate->hashtable = BuildTupleHashTableExt(&fc_rustate->ps,
												fc_desc,
												fc_node->numCols,
												fc_node->dupColIdx,
												fc_rustate->eqfuncoids,
												fc_rustate->hashfunctions,
												fc_node->dupCollations,
												fc_node->numGroups,
												0,
												fc_rustate->ps.state->es_query_cxt,
												fc_rustate->tableContext,
												fc_rustate->tempContext,
												false);
}


/* ----------------------------------------------------------------
 *		ExecRecursiveUnion(node)
 *
 *		顺序扫描递归查询并返回下一个
 *		符合条件的元组。
 *
 * 1. 评估非递归项并将结果赋值给 RT
 *
 * 2. 执行递归项
 *
 * 2.1 WT := RT
 * 2.2 当 WT 不为空时，重复 2.3 到 2.6。如果 WT 为空则返回 RT
 * 2.3 用 WT 替换递归项的名称
 * 2.4 评估递归项并存储到 WT
 * 2.5 将 WT 附加到 RT
 * 2.6 返回 2.2
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_ExecRecursiveUnion(PlanState *fc_pstate)
{
	RecursiveUnionState *fc_node = castNode(RecursiveUnionState, fc_pstate);
	PlanState  *fc_outerPlan = outerPlanState(fc_node);
	PlanState  *fc_innerPlan = innerPlanState(fc_node);
	RecursiveUnion *fc_plan = (RecursiveUnion *) fc_node->ps.plan;
	TupleTableSlot *fc_slot;
	bool		fc_isnew;

	CHECK_FOR_INTERRUPTS();

	/* 1. 评估非递归项 */
	if (!fc_node->recursing)
	{
		for (;;)
		{
			fc_slot = ExecProcNode(fc_outerPlan);
			if (TupIsNull(fc_slot))
				break;
			if (fc_plan->numCols > 0)
			{
				/* 查找或构建该元组组的哈希表条目 */
				LookupTupleHashEntry(fc_node->hashtable, fc_slot, &fc_isnew, NULL);
				/* 每次哈希表查找后必须重置临时上下文 */
				MemoryContextReset(fc_node->tempContext);
				/* 如果已经看到则忽略元组 */
				if (!fc_isnew)
					continue;
			}
			/* 每个非重复元组进入工作表 ... */
			tuplestore_puttupleslot(fc_node->working_table, fc_slot);
			/* ... 并返回给调用者 */
			return fc_slot;
		}
		fc_node->recursing = true;
	}

	/* 2. 执行递归项 */
	for (;;)
	{
		fc_slot = ExecProcNode(fc_innerPlan);
		if (TupIsNull(fc_slot))
		{
			/* 如果中间表中没有内容则完成 */
			if (fc_node->intermediate_empty)
				break;

			/* 旧工作表完成 ... */
			tuplestore_end(fc_node->working_table);

			/* 中间表变为工作表 */
			fc_node->working_table = fc_node->intermediate_table;

			/* 创建新的空中间表 */
			fc_node->intermediate_table = tuplestore_begin_heap(false, false,
															 work_mem);
			fc_node->intermediate_empty = true;

			/* 重置递归项 */
			fc_innerPlan->chgParam = bms_add_member(fc_innerPlan->chgParam,
												 fc_plan->wtParam);

			/* 并继续从递归项中获取 */
			continue;
		}

		if (fc_plan->numCols > 0)
		{
			/* 查找或构建该元组组的哈希表条目 */
			LookupTupleHashEntry(fc_node->hashtable, fc_slot, &fc_isnew, NULL);
			/* 每次哈希表查找后必须重置临时上下文 */
			MemoryContextReset(fc_node->tempContext);
			/* 如果已经看到则忽略元组 */
			if (!fc_isnew)
				continue;
		}

		/* 否则，元组很好；将其存储在中间表中 ... */
		fc_node->intermediate_empty = false;
		tuplestore_puttupleslot(fc_node->intermediate_table, fc_slot);
		/* ... 并返回它 */
		return fc_slot;
	}

	return NULL;
}

/* ----------------------------------------------------------------
 *		ExecInitRecursiveUnion
 * ----------------------------------------------------------------
 */
RecursiveUnionState *
ExecInitRecursiveUnion(RecursiveUnion *fc_node, EState *fc_estate, int fc_eflags)
{
	RecursiveUnionState *fc_rustate;
	ParamExecData *fc_prmdata;

	/* 检查不支持的标志 */
	Assert(!(fc_eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));

	/*
	 * 创建状态结构
	 */
	fc_rustate = makeNode(RecursiveUnionState);
	fc_rustate->ps.plan = (Plan *) fc_node;
	fc_rustate->ps.state = fc_estate;
	fc_rustate->ps.ExecProcNode = fc_ExecRecursiveUnion;

	fc_rustate->eqfuncoids = NULL;
	fc_rustate->hashfunctions = NULL;
	fc_rustate->hashtable = NULL;
	fc_rustate->tempContext = NULL;
	fc_rustate->tableContext = NULL;

	/* 初始化处理状态 */
	fc_rustate->recursing = false;
	fc_rustate->intermediate_empty = true;
	fc_rustate->working_table = tuplestore_begin_heap(false, false, work_mem);
	fc_rustate->intermediate_table = tuplestore_begin_heap(false, false, work_mem);

	/*
	 * 如果使用哈希，则需要为每个元组分配一个内存上下文以进行比较，并且
	 * 需要一个更长生命周期的上下文来存储哈希表。该表不能只保留在
	 * 每个查询上下文中，因为我们希望能够在重新扫描时将其丢弃。
	 */
	if (fc_node->numCols > 0)
	{
		fc_rustate->tempContext =
			AllocSetContextCreate(CurrentMemoryContext,
								  "RecursiveUnion",
								  ALLOCSET_DEFAULT_SIZES);
		fc_rustate->tableContext =
			AllocSetContextCreate(CurrentMemoryContext,
								  "RecursiveUnion hash table",
								  ALLOCSET_DEFAULT_SIZES);
	}

	/*
	 * 通过为其保留的 Param 插槽使状态结构对后代 WorkTableScan 节点可用。
	 */
	fc_prmdata = &(fc_estate->es_param_exec_vals[fc_node->wtParam]);
	Assert(fc_prmdata->execPlan == NULL);
	fc_prmdata->value = PointerGetDatum(fc_rustate);
	fc_prmdata->isnull = false;

	/*
	 * 杂项初始化
	 *
	 * RecursiveUnion 计划没有表达式上下文，因为它们从不调用
	 * ExecQual 或 ExecProject。
	 */
	Assert(fc_node->plan.qual == NIL);

	/*
	 * RecursiveUnion 节点仍然有 Result 插槽，保存元组的指针，
	 * 所以我们必须初始化它们。
	 */
	ExecInitResultTypeTL(&fc_rustate->ps);

	/*
	 * 初始化结果元组类型。 （注意：我们必须在初始化子节点之前设置
	 * 结果类型，因为 nodeWorktablescan.c 期望其有效。）
	 */
	fc_rustate->ps.ps_ProjInfo = NULL;

	/*
	 * 初始化子节点
	 */
	outerPlanState(fc_rustate) = ExecInitNode(outerPlan(fc_node), fc_estate, fc_eflags);
	innerPlanState(fc_rustate) = ExecInitNode(innerPlan(fc_node), fc_estate, fc_eflags);

	/*
	 * 如果使用哈希，预计算内循环的 fmgr 查找数据，并创建
	 * 哈希表。
	 */
	if (fc_node->numCols > 0)
	{
		execTuplesHashPrepare(fc_node->numCols,
							  fc_node->dupOperators,
							  &fc_rustate->eqfuncoids,
							  &fc_rustate->hashfunctions);
		fc_build_hash_table(fc_rustate);
	}

	return fc_rustate;
}

/* ----------------------------------------------------------------
 *		ExecEndRecursiveUnion
 *
 *		释放通过 C 例程分配的任何存储。
 * ----------------------------------------------------------------
 */
void ExecEndRecursiveUnion(RecursiveUnionState *fc_node)
{
	/* 释放元组存储 */
	tuplestore_end(fc_node->working_table);
	tuplestore_end(fc_node->intermediate_table);

	/* 释放附属内容，包括哈希表 */
	if (fc_node->tempContext)
		MemoryContextDelete(fc_node->tempContext);
	if (fc_node->tableContext)
		MemoryContextDelete(fc_node->tableContext);

	/*
	 * 关闭子计划
	 */
	ExecEndNode(outerPlanState(fc_node));
	ExecEndNode(innerPlanState(fc_node));
}

/* ----------------------------------------------------------------
 *		ExecReScanRecursiveUnion
 *
 *		重新扫描关系。
 * ----------------------------------------------------------------
 */
void ExecReScanRecursiveUnion(RecursiveUnionState *fc_node)
{
	PlanState  *fc_outerPlan = outerPlanState(fc_node);
	PlanState  *fc_innerPlan = innerPlanState(fc_node);
	RecursiveUnion *fc_plan = (RecursiveUnion *) fc_node->ps.plan;

	/*
	 * 设置递归项的 chgParam，告知其我们将修改工作
	 * 表，因此它必须重新扫描。
	 */
	fc_innerPlan->chgParam = bms_add_member(fc_innerPlan->chgParam, fc_plan->wtParam);

	/*
	 * 如果子节点的 chgParam 不为 null，则计划将通过第一个 ExecProcNode 重新扫描。 
	 * 因此，以上内容仅适用于非递归项。
	 */
	if (fc_outerPlan->chgParam == NULL)
		ExecReScan(fc_outerPlan);

	/* 释放任何哈希表存储 */
	if (fc_node->tableContext)
		MemoryContextResetAndDeleteChildren(fc_node->tableContext);

	/* 如果需要，清空哈希表 */
	if (fc_plan->numCols > 0)
		ResetTupleHashTable(fc_node->hashtable);

	/* 重置处理状态 */
	fc_node->recursing = false;
	fc_node->intermediate_empty = true;
	tuplestore_clear(fc_node->working_table);
	tuplestore_clear(fc_node->intermediate_table);
}
